﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using eProject.Model;
using EventHandler=eProject.Model.EventHandler;

namespace eProject.ViewModel
{
    public class CommonManager
    {

        public CommonManager()
        {
            _storage = new Storage();

            _tasks = new ObservableCollection<TaskViewModel>();            
            _activities = new ObservableCollection<ActivityViewModel>();
            _employees = new ObservableCollection<EmployeeViewModel>();
            _scheduleItems = new ObservableCollection<ScheduleItemViewModel>();
            Tasks = new ReadOnlyObservableCollection<TaskViewModel>(_tasks);            
            Activities = new ReadOnlyObservableCollection<ActivityViewModel>(_activities);
            Employees = new ReadOnlyObservableCollection<EmployeeViewModel>(_employees);
            ScheduleItems = new ReadOnlyObservableCollection<ScheduleItemViewModel>(_scheduleItems);


            _schedule = new Schedule(_storage);
            _activitiesManager = new ActivitiesManager(_storage, _schedule);           

            Period = new Period(DateTime.Now.Date, DateTime.Now.Date.AddDays(21)); //TODO default period
        }

        public CommonManager(Stream stream) : this()
        {
            Load(stream);
        }

        public ActivityViewModel CreateActivity(TaskViewModel task, EmployeeViewModel employee, Period period, int priority, string description)
        {
            if (task == null) throw new ArgumentNullException("task");
            if (employee == null) throw new ArgumentNullException("employee");
            if (period == null) throw new ArgumentNullException("period");
            if (!Employees.Contains(employee)) throw new ArgumentException("The CommonManager doesn't contain this employee");
            if (!_tasks.Contains(task)) throw new ArgumentException("The CommonManager doesn't contain this task");
            var act = new Activity(task.Task, employee.Employee, period, priority, description);
            _storage.Activities.Add(act);
            var actvm = new ActivityViewModel(this, task, employee, act);            
            _activities.Add(actvm);

            actvm.PropertyChanged += ActivityPropertyChangedHandler;
            
            RefreshPeriod(period);

            return actvm;
        }

        private void ActivityPropertyChangedHandler(object sender, PropertyChangedEventArgs args)
        {            
            if (args.PropertyName == "Period")
            {
                RefreshPeriod();//TODO optimize it?
            }
        }

        public void DeleteActivity(ActivityViewModel activity)
        {
            if (activity == null) throw new ArgumentNullException("activity");
            if (!_activities.Contains(activity)) throw new ArgumentException("The CommonManager doesn't contain this activity");
            _storage.Activities.Remove(activity.Activity);
            _activities.Remove(activity);            
            activity.Unlink();
            activity.PropertyChanged -= ActivityPropertyChangedHandler;
            if (activity.Period.Begin.Equals(Period.Begin) || activity.Period.End.Equals(Period.End)) RefreshPeriod();
            
        }       

        public EmployeeViewModel CreateEmployee(string name, string surname, string phoneNumber, string email)
        {
            return CreateEmployee(name, surname, phoneNumber, email, _employees.Count);//TODO check it
        }

        internal EmployeeViewModel CreateEmployee(string name, string surname, string phoneNumber, string email, int index)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (surname == null) throw new ArgumentNullException("surname");
            if (phoneNumber == null) throw new ArgumentNullException("phoneNumber");
            if (email == null) throw new ArgumentNullException("email");
            var empl = new Employee(name, surname, email, phoneNumber);
            var emplvm = new EmployeeViewModel(this, empl);
            _storage.Employees.Insert(index, empl);
            _employees.Insert(index, emplvm);

            return emplvm;
        }

        public void DeleteEmployee(EmployeeViewModel employee)
        {
            if (_activities.Where(a => a.Employee == employee).Count() > 0) 
                throw new Exception("Employee is working. you cannot delete this employee");
            employee.Unlink();            

            _employees.Remove(employee);
            _storage.Employees.Remove(employee.Employee);
        }

        public void DeleteEmployee(string email)
        {
            var empl = GetEmployee(email);
            if (empl != null) DeleteEmployee(empl);
        }

        public EmployeeViewModel GetEmployee(string email)
        {
            return Employees.First(e => e.EMail.Equals(email));
        }

        public void SwitchEmployees(EmployeeViewModel empl1 , EmployeeViewModel empl2)
        {
            SwitchItems(_employees, empl1, empl2);
        }

        internal void AddScheduleItem(ScheduleItemViewModel item, int index)
        {
            var emails = item.Employees.Select(e => e.EMail);
            var schitem = new ScheduleItem(item.Period, item.ManHours, emails);
            item.ScheduleItem = schitem;
            _storage.ScheduleItems.Insert(index,schitem);
            _scheduleItems.Insert(index,item);            
        }

        public void AddScheduleItem(ScheduleItemViewModel item)
        {
            AddScheduleItem(item, ScheduleItems.Count);
        }

        public void DeleteScheduleItem(ScheduleItemViewModel scheduleItem)
        {
            _storage.ScheduleItems.Remove(scheduleItem.ScheduleItem);
            _scheduleItems.Remove(scheduleItem);
        }

        public void MoveScheduleItem(ScheduleItemViewModel item, int index)
        {
            var oldIndex = _scheduleItems.IndexOf(item);
            var oldIndex2 = _storage.ScheduleItems.IndexOf(item.ScheduleItem);
            _scheduleItems.Move(oldIndex, index);
            _storage.ScheduleItems.Move(oldIndex2,index);
        }

        internal TaskViewModel CreateTask(string name, string description, long duration, int index)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (description == null) throw new ArgumentNullException("description");
            if (duration < 0) throw new ArgumentOutOfRangeException("duration","Duration must be greater than zero");
            if (name == "") throw new ArgumentOutOfRangeException("name", "The Name cannot be empty string");
            var task = new Task(name, description, duration);
            var taskvm = new TaskViewModel(this, task);
            _storage.Tasks.Insert(index, task);
            _tasks.Insert(index, taskvm);
            return taskvm;
        }

        public TaskViewModel CreateTask(string name, string description, long duration)
        {
            return CreateTask(name, description, duration, Tasks.Count);
        }

        public void DeleteTask(TaskViewModel task)
        {
            if (task == null) throw new ArgumentNullException("task");
            if (!_tasks.Contains(task)) throw new ArgumentException("The CommonManager doesn't contain this task");
            _tasks.Remove(task);
            _storage.Tasks.Remove(task.Task);
            task.Unlink();
            //var acts = _activities.Where(a => a.Task == task).ToList();
            //foreach (var act in acts)
            //{
            //    DeleteActivity(act);
            //}
        }

        public void DeleteTask(string name)
        {
            var task = GetTask(name);
            if (task != null) DeleteTask(task);
        }

        public TaskViewModel GetTask(string name)
        {
            return Tasks.Count == 0 ? null : Tasks.First(t => t.Name.Equals(name));
        }

        public void SwitchTasks(TaskViewModel task1, TaskViewModel task2)
        {
            SwitchItems(_tasks, task1, task2);
        }

        private static void SwitchItems<T>(ObservableCollection<T> collection, T item1, T item2)
        {
            var index1 = collection.IndexOf(item1);
            var index2 = collection.IndexOf(item2);
            if (index1 == -1 || index2 == -1) return; //TODO log

            if (index1 < index2)
            {
                collection.Move(index1, index2);
                collection.Move(index2 - 1, index1);    
            }
            else
            {
                collection.Move(index1, index2);
                collection.Move(index2 + 1, index1);    
            }
            
        }

        public double GetManHours(TaskViewModel task)
        {
            if (task == null) throw new ArgumentNullException("task");
           // if (!_tasks.Contains(task)) throw new ArgumentException("The CommonManager doesn't contain this task");
            return _activitiesManager.GetManHours(task.Task);
        }

        public double GetManHours(ActivityViewModel activity)
        {
            if (activity == null) throw new ArgumentNullException("activity");
            if (!_activities.Contains(activity)) throw new ArgumentException("The CommonManager doesn't contain this activity");
            return _activitiesManager.GetManHours(activity.Activity);
        }

        public double GetManHours(EmployeeViewModel employee, DateTime date)
        {
            return _schedule.GetManHours(employee.Employee, date);
        }

        public bool IsDayOff(DateTime date)
        {
            return _schedule.IsDayOff(date);
        }


        /// <summary>
        /// Returns work hours for the date.
        /// </summary>
        /// <param name="date">A date</param>
        /// <param name="employee">An Employee. May be null</param>
        /// <returns></returns>
        public bool IsDayOff(DateTime date, EmployeeViewModel employee)
        {
            var empl = employee != null ? employee.Employee : null;
            return _schedule.GetManHours(empl, date) == 0;
        }

        public double GetBusinessDayLength(DayOfWeek dayOfWeek)
        {
            return _storage.ScheduleSettings.GetManHours(dayOfWeek);
        }

        public void SetBusinessDayLength(DayOfWeek dayOfWeek, double length)
        {
            _storage.ScheduleSettings.SetManHours(dayOfWeek, length);
        }

        public void Save(Stream stream)
        {
            _storage.Save(stream);
        }

        private void Load(Stream stream)
        {
            _storage.Load(stream);

            _tasks.Clear();
            _employees.Clear();
            foreach (var activity in _activities)
            {
                activity.PropertyChanged -= ActivityPropertyChangedHandler;
                _activities.Remove(activity);
            }            

            var tasks = _storage.Tasks;
            var empls = _storage.Employees;
            foreach (var empl in empls)
            {
                _employees.Add(new EmployeeViewModel(this, empl));
            }
            foreach (var task in tasks)
            {
                _tasks.Add(new TaskViewModel(this, task));
            }

            var acts = _storage.Activities;
            foreach (var act in acts)
            {
                var task = _tasks.First(t => t.Task == act.Task);
                var empl = Employees.First(e => e.Employee == act.Employee);
                var actvm = new ActivityViewModel(this, task, empl, act);
                actvm.PropertyChanged += ActivityPropertyChangedHandler;
                _activities.Add(actvm);
            }
            foreach (var task in _tasks)
            {
                task.InitializeAfterRestore();
            }

            var schitems = _storage.ScheduleItems;
            foreach (var item in schitems)
            {
                var employees = item.EmployeesEmails.Count()>0?Employees.Where(e => item.EmployeesEmails.Contains(e.EMail)).ToList():new List<EmployeeViewModel>(0);
                _scheduleItems.Add(new ScheduleItemViewModel(employees,item.ManHours,item.Period){ScheduleItem = item});
            }

            RefreshPeriod();

        }

        internal IEnumerable<double> GetDetails(ActivityViewModel activity)
        {
            return _activitiesManager.GetDetails(activity.Activity);
        }

        public Period Period { get; private set; }        

        public ReadOnlyObservableCollection<ActivityViewModel> Activities { get; private set; }        

        public ReadOnlyObservableCollection<TaskViewModel> Tasks { get; private set; }

        public ReadOnlyObservableCollection<EmployeeViewModel> Employees { get; private set; }

        public ReadOnlyObservableCollection<ScheduleItemViewModel> ScheduleItems { get; private set; }

        private readonly ObservableCollection<ActivityViewModel> _activities;

        private readonly ObservableCollection<TaskViewModel> _tasks;

        private readonly ObservableCollection<EmployeeViewModel> _employees;

        private readonly ObservableCollection<ScheduleItemViewModel> _scheduleItems;

        private readonly ActivitiesManager _activitiesManager;

        private readonly Schedule _schedule;

        private readonly Storage _storage;

        internal event ActivitiesManager.ManHoursChangedEventHandler ManHoursChanged
        {
            add { _activitiesManager.ManHoursChanged += value; }
            remove { _activitiesManager.ManHoursChanged -= value; }
        }

        public event EventHandler ScheduleChanged
        {
            add { _schedule.ScheduleChanged += value; }
            remove { _schedule.ScheduleChanged -= value; }
        }

       

        public event EventHandler<Period,Period> PeriodChanged;

        private void InvokePeriodChanged(Period oldOne, Period newOne)
        {
            var changed = PeriodChanged;
            if (changed != null) changed(oldOne, newOne);
        }

        private void RefreshPeriod(Period period)
        {
            var old = Period;
            if (Activities.Count == 1)
            {
                Period = Activities[0].Period;
                InvokePeriodChanged(old, Period);

            }
            else
            {
                var newBegin = period.Begin < Period.Begin ? period.Begin : Period.Begin;
                var newEnd = period.End > Period.End ? period.End : Period.End;
                if (!newBegin.Equals(Period.Begin) || !newEnd.Equals(Period.End))
                {
                    Period = new Period(newBegin, newEnd);
                    InvokePeriodChanged(old, Period);
                }
            }
        }
        private void RefreshPeriod()
        {
            var old = Period;
            if (_activities.Count == 0)
            {
                Period = new Period(DateTime.Now.Date, DateTime.Now.AddDays(7).Date);
            }
            else
            {
                var begin = _activities.Min(a => a.Period.Begin);
                var end = _activities.Max(a => a.Period.End);                
                Period = new Period(begin, end);
            }
            InvokePeriodChanged(old, Period);
        }
    }
}
